<script setup lang="ts">
import { computed, nextTick, reactive, ref, watch } from 'vue'

const props = defineProps({
  modelValue: null, // any | any[]
  props: {
    type: Object,
    default: () => ({})
  },
  disabled: {
    type: Boolean,
    default: false
  },
  dataMethod: {
    type: Function,
    default: async () => []
  },
  multiple: {
    type: Boolean,
    default: false
  },
  headerData: {
    type: Array as any,
    default: () => [
      {
        label: 'id',
        prop: 'id',
        default: null, // 默认查询值
        queryable: false, // 查询框
        hidden: false, // 隐藏表头
        type: null, // null | select
        // type == select 时的属性
        data: [],
        valueKey: 'id',
        props: { value: 'id', label: '', children: 'children' }
      }
    ]
  },
  cacheData: {
    type: Array as any,
    default: () => []
  },
  dialogWidth: {
    type: [String, Number],
    default: '50%'
  }
})

const emit = defineEmits(['update:modelValue', 'close', 'change', 'add', 'remove'])

const open = ref(false)
const loading = ref(false)
const list = ref([])
const selectedValue: any = ref([])
const total = ref(0)
const queryRef = ref()
const queryParams: any = ref({})
const tableRef = ref()
const tagsMap = reactive(new Map())
// 字段映射
const propsProps = computed(() => {
  return {
    value: props.props.value ?? 'id', // modelValue
    label: props.props.label ?? 'label',
    rows: props.props.rows ?? 'rows',
    total: props.props.total ?? 'total',
    currentPage: props.props.currentPage ?? 'currentPage',
    pageSize: props.props.pageSize ?? 'pageSize'
  }
})

watch(
  () => props.modelValue,
  () => {
    if (props.multiple) selectedValue.value = [...props.modelValue]
    else selectedValue.value = props.modelValue
  },
  { immediate: true }
)

// 获取标签
const getLabel = (value) => {
  if (tagsMap.get(value)) return tagsMap.get(value)[propsProps.value.label]
  else {
    const cacheItem = props.cacheData.find((item) => item[propsProps.value.value] == value)

    if (cacheItem) return cacheItem[propsProps.value.label]
    else return value
  }
}
// 过滤查询参数
const transformParams = (params) => {
  return Object.entries(params).reduce((pre: any, cur) => {
    if (!(cur[1] === null || cur[1] === undefined || cur[1] === '')) pre[cur[0]] = cur[1]
    return pre
  }, {})
}
// 选择行
const toSelect = () => {
  const value = selectedValue.value

  if (props.multiple) {
    list.value.forEach((row) => {
      if (value.includes(row[propsProps.value.value])) {
        nextTick(() => {
          tableRef.value?.toggleRowSelection(row, true)
        })
      }
    })
  } else {
    list.value.forEach((row) => {
      if (value === row[propsProps.value.value]) {
        nextTick(() => {
          tableRef.value?.setCurrentRow(row)
        })
      }
    })
  }
}
// 获取数据
const getList = () => {
  loading.value = true

  const params = transformParams(queryParams.value)

  props.dataMethod(params).then((res) => {
    list.value = res[propsProps.value.rows]
    total.value = res[propsProps.value.total]
    loading.value = false

    toSelect()
  })
}
// 查询
const handleQuery = () => {
  queryParams.value[propsProps.value.currentPage] = 1

  getList()
}
const resetQuery = () => {
  queryRef.value.resetFields()

  handleQuery()
}
// 生成查询参数
const resetQueryParams = () => {
  queryParams.value = props.headerData.reduce(
    (pre, cur) => {
      if (cur.queryable) pre[cur.prop] = cur.default ?? null
      return pre
    },
    { [propsProps.value.currentPage]: 1, [propsProps.value.pageSize]: 10 }
  )
}

watch(open, () => {
  resetQueryParams()
})

watch(
  () => props.headerData,
  () => {
    resetQueryParams()
  },
  { immediate: true }
)

const toSearch = () => {
  open.value = true

  handleQuery()
}
// 单选
const handleCurrentChange = (row) => {
  if (props.multiple) return

  if (row) selectedValue.value = row[propsProps.value.value]

  emit('update:modelValue', selectedValue.value)
  emit('change', selectedValue.value)
  emit('add', selectedValue.value)
}
// 添加多选值
const addValue = (row) => {
  const value = row[propsProps.value.value]

  tagsMap.set(value, row)

  if (!selectedValue.value.includes(value)) {
    selectedValue.value.push(value)
  }

  emit('update:modelValue', selectedValue.value)
  emit('change', selectedValue.value)
  emit('add', value)
}
// 删除多选值
const removeValue = (value) => {
  const index = selectedValue.value.indexOf(value)
  if (index !== -1) {
    selectedValue.value.splice(index, 1)
  }

  tagsMap.delete(value)

  const row = list.value.find((row) => value === row[propsProps.value.value])
  if (row) {
    nextTick(() => {
      tableRef.value?.toggleRowSelection(row, false)
    })
  }

  emit('update:modelValue', selectedValue.value)
  emit('change', selectedValue.value)
  emit('remove', value)
}
// 多选
const handleSelect = (rows, row) => {
  if (!props.multiple) return

  if (rows.includes(row)) addValue(row)
  else removeValue(row[propsProps.value.value])
}
// 多选行点击
const hanldeRowClick = (row) => {
  if (!props.multiple) return

  tableRef.value?.toggleRowSelection(row)
  handleSelect(tableRef.value?.getSelectionRows(), row)
}
// 全选
const handleSelectAll = (rows) => {
  if (!props.multiple) return

  if (rows.length > 1) list.value.forEach((row) => addValue(row))
  else list.value.forEach((row) => removeValue(row[propsProps.value.value]))
}
</script>

<template>
  <div class="search-box-container">
    <div v-if="$slots.default" @click="toSearch">
      <slot name="default"></slot>
    </div>
    <el-button v-else :disabled="disabled" icon="Search" @click="toSearch" />
    <el-dialog v-model="open" append-to-body :width="props.dialogWidth" @close="emit('close')">
      <el-form ref="queryRef" class="form" :model="queryParams" :inline="true">
        <template v-for="item of headerData">
          <el-form-item
            v-if="item.queryable"
            :key="item.prop"
            :label="item.label"
            :prop="item.prop"
          >
            <el-tree-select
              v-if="item.type == 'select'"
              v-model="queryParams[item.prop]"
              style="width: 150px"
              :data="item.data"
              :props="item.props"
              :value-key="item.valueKey"
              :placeholder="`请选择${item.label}`"
              check-strictly
              clearable
            />
            <el-input
              v-else
              v-model="queryParams[item.prop]"
              :placeholder="`请输入${item.label}`"
              @keydown.enter.prevent="handleQuery"
            />
          </el-form-item>
        </template>
        <el-form-item>
          <el-button type="primary" icon="Search" circle @click="handleQuery"> </el-button>
          <el-button icon="Refresh" circle @click="resetQuery"></el-button>
        </el-form-item>
      </el-form>

      <template v-if="props.multiple">
        <el-tag
          v-for="item in selectedValue"
          :key="item"
          class="tags"
          closable
          @close="removeValue(item)"
        >
          {{ getLabel(item) }}
        </el-tag>
      </template>

      <el-table
        ref="tableRef"
        v-loading="loading"
        :data="list"
        border
        :highlight-current-row="!multiple"
        @current-change="handleCurrentChange"
        @select="handleSelect"
        @select-all="handleSelectAll"
        @row-click="hanldeRowClick"
      >
        <el-table-column v-if="props.multiple" type="selection" width="55" />
        <template v-for="item of headerData" :key="item.prop">
          <el-table-column
            v-if="!item.hidden"
            :label="item.label"
            :prop="item.prop"
            align="center"
          />
        </template>
      </el-table>

      <el-pagination
        v-model:current-page="queryParams[propsProps.currentPage]"
        v-model:page-size="queryParams[propsProps.pageSize]"
        size="small"
        layout="prev, pager, next"
        :total="total"
        @current-change="getList"
      />
    </el-dialog>
  </div>
</template>

<style scoped>
.search-box-container {
  display: inline-flex;
}

.form {
  margin-bottom: 20px;
}

.tags {
  margin-bottom: 20px;
}

:deep(.el-table__body tr.current-row > td.el-table__cell) {
  background-color: var(--el-color-primary);
  color: #fff;
}
</style>
